package com.wisecoach.gatewayplus.spring.boot.autoconfigure;

import com.wisecoach.gatewayplus.info.GatewayInfoProvider;
import com.wisecoach.gatewayplus.session.GatewaySessionProvider;
import com.wisecoach.gatewayplus.spring.interceptor.ChaincodeTransactionInterceptor;
import com.wisecoach.gatewayplus.spring.interceptor.TransactionAttributeSourceAdvisor;
import com.wisecoach.gatewayplus.spring.seata.DefaultTCCLogicExecutor;
import com.wisecoach.gatewayplus.spring.seata.TCCChaincodeTxInterceptor;
import com.wisecoach.gatewayplus.spring.seata.TCCTxAttributeSourceAdvisor;
import com.wisecoach.gatewayplus.transaction.*;
import com.wisecoach.gatewayplus.transaction.async.SubmitAsyncAdvice;
import com.wisecoach.gatewayplus.transaction.async.SubmitAsyncContractExecutor;
import com.wisecoach.gatewayplus.transaction.lockedtx.*;
import com.wisecoach.gatewayplus.transaction.seata.SeataTransactionContextProvider;
import com.wisecoach.gatewayplus.transaction.sync.SubmitSyncAdvice;
import com.wisecoach.gatewayplus.transaction.sync.SubmitSyncContractExecutor;
import com.wisecoach.gatewayplus.transaction.tx.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.util.Map;

/**
 * {@code @author:} wisecoach
 * {@code @date:} 2023/6/15 下午4:18
 * {@code @version:} 1.0.0
 */


@Configuration
@EnableConfigurationProperties({GatewayPlusTxProperties.class})
public class GatewayPlusTxAutoConfiguration {

    private final GatewayPlusTxProperties txProperties;

    public GatewayPlusTxAutoConfiguration(GatewayPlusTxProperties txProperties) {
        this.txProperties = txProperties;
    }


    // ------------------- transaction --------------------

    @Bean
    @Primary
    public ContractExecutorDelegate contractExecutor() {
        return new ContractExecutorDelegate();
    }

    @Bean
    @Primary
    public TransactionAdviceDelegate transactionAdvice() {
        return new TransactionAdviceDelegate();
    }

    @Bean
    @ConditionalOnMissingBean
    public TransactionContextProvider localTransactionContextProvider() {
        return new LocalTransactionContextProvider();
    }

    // ------------------ transaction submit-sync --------

    @Bean
    public SubmitSyncContractExecutor submitSyncContractExecutor(
            ContractExecutorDelegate delegate) {
        SubmitSyncContractExecutor executor = new SubmitSyncContractExecutor();
        delegate.register(TransactionStrategy.SUBMIT_SYNC, executor);
        return executor;
    }

    @Bean
    public SubmitSyncAdvice submitSyncAdvice(
            TransactionAdviceDelegate delegate
    ) {
        SubmitSyncAdvice advice = new SubmitSyncAdvice();
        delegate.register(TransactionStrategy.SUBMIT_SYNC, advice);
        return advice;
    }

    // ------------------ transaction submit-async --------

    @Bean
    public SubmitAsyncContractExecutor submitAsyncContractExecutor(
            ContractExecutorDelegate delegate) {
        SubmitAsyncContractExecutor executor = new SubmitAsyncContractExecutor();
        delegate.register(TransactionStrategy.SUBMIT_ASYNC, executor);
        return executor;
    }

    @Bean
    public SubmitAsyncAdvice submitAsyncAdvice(
            TransactionAdviceDelegate delegate
    ) {
        SubmitAsyncAdvice advice = new SubmitAsyncAdvice();
        delegate.register(TransactionStrategy.SUBMIT_ASYNC, advice);
        return advice;
    }

    // ------------------ transaction submit-tx --------

    @Bean
    public SubmitTxResource submitTxResource(InvalidTransactionHandler invalidTransactionHandler) {
        TxSubmittedTxProcessor txProcessor = new TxSubmittedTxProcessor(invalidTransactionHandler);
        return new SubmitTxResource(txProcessor);
    }

    @Bean
    public SubmitTxContractExecutor submitTxContractExecutor(
            SubmitTxResource resource,
            ContractExecutorDelegate delegate
    ) {
        SubmitTxContractExecutor executor = new SubmitTxContractExecutor(resource);
        delegate.register(TransactionStrategy.TX_SUBMIT, executor);
        return executor;
    }

    @Bean
    public SubmitTxAdvice submitTxAdvice(
            SubmitTxResource resource,
            TransactionAdviceDelegate delegate
    ) {
        SubmitTxConsumer consumer = new SubmitTxConsumer(resource);
        SubmitTxAdvice advice = new SubmitTxAdvice(consumer);
        delegate.register(TransactionStrategy.TX_SUBMIT, advice);
        return advice;
    }

    // ------------------ transaction locked-tx --------

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "fabric.gatewayplus.tx.lock.type", havingValue = "memory", matchIfMissing = true)
    public LockSource lockSource() {
        return new MemoryLockSource();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(org.springframework.data.redis.core.RedisTemplate.class)
    @ConditionalOnProperty(value = "fabric.gatewayplus.tx.lock.type", havingValue = "redis")
    static class RedisLockConfiguration {
        @Bean
        public LockSource redisLockSource(org.springframework.data.redis.core.RedisTemplate<String, Map<String, RWLock>> redisTemplate) {
            return new RedisLockSource(redisTemplate);
        }
    }

    @Bean
    public LockedTxResource lockedTxResource(LockSource lockSource, InvalidTransactionHandler invalidTransactionHandler) {
        LockedSubmittedTxProcessor txProcessor = new LockedSubmittedTxProcessor(invalidTransactionHandler);
        LockedTxResource resource = new LockedTxResource(lockSource, txProcessor);
        txProcessor.setResource(resource);
        return resource;
    }

    @Bean
    public LockedTxContractExecutor lockedTxContractExecutor(LockedTxResource resource,
                                                             ContractExecutorDelegate delegate) {
        GatewayPlusTxProperties.LockedTxProperties lockedTxProperties = txProperties.getLockedTx();
        LockedTxContractExecutor executor = new LockedTxContractExecutor(resource, lockedTxProperties.getMaxRetry(), lockedTxProperties.getRetryInterval());
        delegate.register(TransactionStrategy.LOCKED_TX_SUBMIT, executor);
        return executor;
    }

    @Bean
    public LockedTxAdvice lockedTxAdvice(
            LockedTxResource resource,
            TransactionAdviceDelegate delegate
    ) {
        LockedTxConsumer consumer = new LockedTxConsumer(resource);
        LockedTxAdvice advice = new LockedTxAdvice(consumer);
        delegate.register(TransactionStrategy.LOCKED_TX_SUBMIT, advice);
        return advice;
    }

    // ------------------- 通用配置 -----------------------

    @Bean
    @ConditionalOnMissingBean(value = {InvalidTransactionHandler.class})
    public InvalidTransactionHandler noOpInvalidTransactionHandler() {
        return new NoOpInvalidTransactionHandler();
    }

    // ------------------- spring-service -----------------

    @Bean
    public TransactionAttributeSource fabricTransactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "fabric.gatewayplus.tx.type", havingValue = "local", matchIfMissing = true)
    public TransactionAttributeSourceAdvisor transactionAttributeSourceAdvisor(
            TransactionAttributeSource source,
            TransactionAdvice advice,
            TransactionContextProvider contextProvider,
            GatewayInfoProvider gatewayInfoProvider,
            GatewaySessionProvider gatewaySessionProvider) {
        ChaincodeTransactionInterceptor chaincodeTransactionInterceptor = new ChaincodeTransactionInterceptor(source, advice, contextProvider, gatewayInfoProvider, gatewaySessionProvider);
        return new TransactionAttributeSourceAdvisor(chaincodeTransactionInterceptor);
    }

    /**
     * 在使用seata时启用
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(io.seata.core.context.RootContext.class)
    @ConditionalOnProperty(name = "fabric.gatewayplus.tx.type", havingValue = "seata")
    static class SeataConfiguration {

        @Bean
        public TCCTxAttributeSourceAdvisor tccTxAttributeSourceAdvisor(
                TransactionAttributeSource source,
                TransactionAdvice advice,
                TransactionContextProvider contextProvider,
                GatewayInfoProvider gatewayInfoProvider,
                GatewaySessionProvider gatewaySessionProvider) {
            DefaultTCCLogicExecutor tccLogicExecutor = new DefaultTCCLogicExecutor(source, advice, contextProvider, gatewayInfoProvider, gatewaySessionProvider);
            TCCChaincodeTxInterceptor tccChaincodeTxInterceptor = new TCCChaincodeTxInterceptor(tccLogicExecutor, source);
            return new TCCTxAttributeSourceAdvisor(tccChaincodeTxInterceptor);
        }

        @Bean
        public TransactionContextProvider seataTransactionContextProvider() {
            return new SeataTransactionContextProvider();
        }
    }

}
